This paper describes two new sets of related functionality for Carbon applications. CFBundle and CFPlugIn.
The CFBundle type represents a Mac OS X "bundle" in your application. A bundle is a directory laid out according to the new application packaging layout containing resources and/or executable code and is the primary way of packaging things on Mac OS X. Application, frameworks, and loadable plug-ins are all bundles. The CFPlugIn API layers on top of CFBundle and provides a basic architecture for applications that need to support plug-ins.
CFBundle has two main areas of functionality. The first thing CFBundle does is provide API for finding "resources" within a bundle. From CFBundle's point of view, resources are primarily files rather than Resource Manager-style resources. However, some of these files may contain Resource Manager-style resources, and CFBundle contains API to support multiply localized resource Resource Manager-style resources. CFBundle's resource API encapsulates the knowledge of how resources are stored in a bundle and how to find them. This knowledge is supplied to the clients of CFBundle in the form of Universal Resource Locators, contained in CFURLs.
The other thing CFBundle does is provide API which deals with primitive dynamic code loading/unloading and function lookup. This API basically provides an abstraction for dealing with loadable code without having to know which binary format and platform API should be used. On Mac OS 8 or 9 CFBundle knows how to load code using CFM. On Mac OS X it knows how to use either dyld or CFM as appropriate.
CFPlugIn defines a higher-level PlugIn API above and beyond the simple code loading and function lookup of CFBundle. This level of API defines some policy and plumbing for applications that need to load plug-ins. This API is implemented on top of the primitive code-loading facilities of CFBundle. It is intended mainly for developers designing new plug-in architectures. It is not intended to be compatible with or mappable to all the various ad-hoc plug-in schemes that individual app developers have designed. CFPlugIn's architecture is compatible with the basic aspects of Microsoft's COM specification.
The final packaging details for bundles on Mac OS X has not been finalized yet, but if you use the CFBundle API, the actual layout will not matter to your application. The actual packaging proposal is described in the App Packaging document.
Each bundle can have resources. Resources can be global or localizable. Global resources need no translation and there is only one copy of each one. Localizable resources need to be translated for different regions or languages (a region is defined by a specific language and a specific locale). There is one copy of each localized resource for each region/language that a bundle has been translated for. All these translations are packaged together in the bundle so all localizations are available at all times. This means that different users of a Mac OS X application may have different language preferences and they will each see that application's resources in their own preferred language. Resources can also be platform specific (and this is independent of whether the resource is global or localizable). There might be different versions of certain resources for different platforms. For instance, an image resource may need to have one appearance on Mac OS X and another appearance on Mac OS 8 or 9.
In addition to Resources, each bundle can contain one (or more) executables. (In this context and executable is an application, a shared library, or a dynamically loadable plug-in.) A bundle can contain one copy of each executable it requires for each platform that the bundle needs to support. So a Carbon application can have both a Mac OS 8/9 binary and an Mac OS X binary inside of it and the appropriate one will be used depending on what platform the bundle is running on. A bundle will generally have a "main" executable. This is the actual application binary for an application bundle, or the shared library for a framework, etc... In addition, auxiliary executables can be packaged with a bundle. An application that requires some extra helper tool binaries to accomplish some things can store these tools inside itself, per-platform, just like the main executable and can look up the appropriate helper tool for the platform it is running on. Auxiliary executables should be only those executables that are not themselves bundles. If your application had a framework that it required, that would be packaged in the application as a nested framework bundle, not as an auxiliary executable; likewise, a required loadable bundle would be packaged as a nested loadable bundle.
Each bundle has two special files in it. The first one is called the Info property list (plist) or Info dictionary. This is a file containing an XML property list that describes various aspects of the bundle. The Info plist contains information such as the name of the main executable for the bundle, version info, type and creator codes, and other meta-data that CFBundle needs. In addition, subsystems layered on top of CFBundle may define their own information to be contained in a bundle's Info plist and easily access it at run time. Developers are free to store other application-defined data in the Info plist as well. The other special file is the "PkgInfo" file. This is a very simple file that contains (redundantly) the type and creator for the bundle. This information is stored in the "PkgInfo" file as well as the Info.plist for performance reasons.
There is also a special localized resource that goes with the Info.plist called InfoPlist.strings which can contain Info.plist keys that need to be localized such as the CFBundleName key.
Please see the InfoPlist.html release note for details on the format and contents of the Info.plist file.
Before we look at the specific features of CFBundle, let's look at how bundles are created and found in an application.
To create a CFBundle, all you need is a URL that describes where the bundle is. You can then create a CFBundle like this:
CFBundleRef myBundle = CFBundleCreate(NULL, someURL);
CFBundles are uniqued according to their URLs. If a CFBundle instance for "someURL" already exists, that instance is retained and returned. Otherwise a new CFBundle instance is created and returned.
One bundle is special. The "main" bundle is the bundle that the running program came from (if any). You can get the CFBundle for it like this:
CFBundleRef mainBundle = CFBundleGetMainBundle();
Since not all programs that use CF will be packaged in bundles, it is possible that mainBundle will be NULL (this might be the case for command line tools, for example). But for an application (Carbon or Cocoa) the main bundle is the application's bundle.
Bundles can have "identifiers". The bundle's identifier is defined in the Info plist. If a bundle has an identifier, it can later be looked up with that identifier. This is useful for code that needs to be able to find resources that are packaged with it. Say you have a plug-in that consists of some code and some images. The images will be resources of the bundle that contains your plug-in code. But how can your code find the images? It cannot know the absolute URL since the user may have installed the plug-in anywhere. But if the plug-in writer gives the plug-in bundle an identifier then the code can use that identifier to find the bundle at runtime and get access to its resources. Bundle identifiers should use the Java package naming conventions (eg "com.xyz.Finder.MyGetInfoPlugIn"). Bundle identifiers need to be unique, so be sure to use the package naming conventions to avoid collision with other bundle writers. Note that the bundle identifier can only be used to locate an existing CFBundle instance (including the main bundle and bundles for all statically linked frameworks). If the code in your bundle is running, you can use the identifier to find it. Here is how you find a bundle given a identifier:
CFBundleRef myBundle = CFBundleGetBundleWithIdentifier(
CFSTR("com.xyz.Finder.FooBundle"));
Here is an example of how the bundle identifier facility might be used. Say that you write a Finder plug-in. You know your Finder plug-in will need access to some resources at runtime so you include those resources in the plug-in's bundle and you give your bundle an identifier ("com.xyz.Finder.MyGetInfoPlugIn"). Now, say that Finder has found your plug-in and loaded it and started executing the code inside it. Now your code is executing and you need to find your resources. First, you would find the bundle by its identifier, and then you would use the resource API (described in more detail below) to find the resource you needed:
CFBundleRef myBundle = CFBundleGetBundleWithIdentifier(
CFSTR("com.xyz.Finder.MyGetInfoPlugIn"));
CFURLRef resourceURL = CFBundleCopyResourceURL(myBundle,
CFSTR("MyImage"), CFSTR("Apple JPEG Image Type"), NULL);
/* Now use resourceURL to load the image */
Notice that in the above example, the plug-in never had to know, by itself, where it was loaded from. It is able to find the CFBundle instance that Finder created when it loaded the bundle in the first place and use it to find resources inside the bundle. This is the main point of using identifiers with bundles.
Identifiers will also be used along with version numbers if you want to use any of the automatic support for bundle versioning. The identifier is used to uniquely identify a bundle, and if two bundles with the same identifier are found, the one with the larger version number would be preferred.
There is also API to obtain an array of all the existing CFBundle instances. This API is potentially expensive and should be used with care, but if you need to get the list of all bundles, you can:
CFArrayRef CFBundleGetAllBundles()
The list of all bundles includes the main bundle, bundles for all statically linked frameworks, and, of course, any CFBundles you actually created with CFBundleCreate().
Once you have a CFBundle, you can find out its actual location as well as the location of its supporting files or resources:
CFURLRef bundleURL = CFBundleCopyBundleURL(myBundle);
CFURLRef bundleURL = CFBundleCopySupportFilesDirectoryURL(myBundle);
CFURLRef bundleURL = CFBundleCopyResourcesDirectoryURL(myBundle);
Usually, you should not care about any of these locations. URLs to specific resources within the bundle should be constructed with the resource finding API described below. If you find yourself wanting to call these functions, think twice.
You can also ask the bundle for its info dictionary, version, identifier, or development region, or for the value of any info dictionary key.
CFDictionaryRef CFBundleGetInfoDictionary(CFBundleRef bundle);
CFStringRef CFBundleGetIdentifier(CFBundleRef bundle);
UInt32 CFBundleGetVersionNumber(CFBundleRef bundle);
CFStringRef CFBundleGetDevelopmentRegion(CFBundleRef bundle);
CFTypeRef CFBundleGetValueForInfoDictionaryKey(CFBundleRef bundle, CFStringRef key);
The difference between using CFBundleGetValueForInfoDictionaryKey and asking the info dictionary directly for the value of a key is that CFBundleGetValueForInfoDictionaryKey will automatically return a localized value, if one is available from the InfoPlist.strings. This is the preferred method for examining the info dictionary. The specific calls CFBundleGetIdentifier, CFBundleGetVersionNumber, and CFBundleGetDevelopmentRegion are also preferable to directly accessing the info dictionary for these keys, since in some cases they will do some extra processing of the value.
The resource API lets you find resources within a bundle. It handles a lot of tricky searching issues, so you should always use it as opposed to groping around inside the bundle yourself. The API also insulates you from potential changes to the bundle packaging scheme. When you ask CFBundle to locate a resource it performs a search to ensure it finds the right version of the resource. Remember that resources can be global or localizable and that there might be platform-specific versions as well. While, internally, the search may be complex, there are only a couple things that a developer needs to be aware of:
If you keep the above rules in mind, you'll do the right thing. It is, perhaps, interesting to know what CFBundle actually does. The actual rules the CFBundle uses internally to search for a resource are as follows:
Note that there are a couple possibly non-obvious things implied by these rules. First, global resources take precedence over localized resources. If there is a global version of a resource, localized versions of that same resource will never be found. The reason for this precedence has to do with performance. If the localizable resources were searched first, we might have to look in at least a couple places before discovering the global resource. Another non-obvious thing is that in order to find a platform-specific resource, the platform-generic version MUST exist. Again, the reason for this is performance. What this means is that you should generally have one platform's version of the resource be the generic one and provide platform specific versions for any other platforms. The resource searching rules are meant to minimize the amount of times we must call PBGetCatInfo() or stat() in order to find a resource.
Although the above discussion sounds somewhat complicated, it is actually very simple from the developer's point of view. To find a resource, the developer just calls CFBundleCopyResourceURL():
CFURLRef resourceURL = CFBundleCopyResourceURL(myBundle, CFSTR("MyImage"),
CFSTR("Apple GIF Image Type"), NULL);
The resource type ("Apple GIF Image Type" in this example) is the abstract type for the resource. Usually you would use a defined string constant representing a known type here. Abstract types can be mapped to file types on HFS or file extensions on other file systems. The abstract type API itself has not yet been formally defined.
There are several other variants of resource lookup. There is a function which can return a list of all the resources of a given type in a bundle. There are also APIs for accessing the resources inside a bundle without actually creating a CFBundleRef. These APIs might be slightly faster than creating a bundle and asking, but only if you only intend to ask once. If you need to query for more than one resource, it is always much cheaper to create the bundle which can cache various information about how to find resources.
One common kind of resource is a strings file. Strings files are used for strings that must be localized. They are basically dictionaries that map a string in the development language to the localized version of the string. (Actually, the key is not required to be the development language version of the string, but usually this convention is used.) Because they are used so commonly, and because it is worth-while to provide caching behavior for performance reasons, CFBundle includes convenience API for dealing with string tables. To look up the localized version of a given string you can do this:
CFStringRef localString = CFBundleCopyLocalizedString(myBundle,
CFSTR("The string to translate"), CFSTR("The string to translate"),
CFSTR("MyStrings"));
The localized string API knows how to locate and load the string table (like any other resource) and then look up the string you want all in one step. It also provides caching so that multiple lookups from the same table do not require relocating and reloading the strings file. In practice, developers should usually use the CFLocalizedString... macros in their code. These macros wind up using the bundle API above, but they provide a couple extra things. First, there are several variants of the macro that are simpler for certain common cases. Also, these macros are recognized by the "genstrings" development tool which can automatically generate strings files from your source code (if it uses these macros). Finally, the macros include a comment string argument which is totally ignored by the compiler, but which genstrings will use to annotate the generated strings file. Here is how the macros are used:
CFStringRef localString = CFLocalizedString(
CFSTR("Development region version of string"),
CFSTR("Comment to help translators by giving them \
context or other hints about how the string is used \
or how to translate it."));
CFStringRef localString = CFLocalizedStringFromTable(
CFSTR("Development region version of string"),
CFSTR("MyStrings"), CFSTR("Comment to help translators \
by giving them context or other hints about how the string \
is used or how to translate it."));
CFStringRef localString = CFLocalizedStringFromTableInBundle(
CFSTR("Development region version of string"),
CFSTR("MyStrings"), myBundle, CFSTR("Comment to help \
translators by giving them context or other hints about \
how the string is used or how to translate it."));
The first version of the macros attempts to find a strings file named "Localizable.strings" in the app's main bundle. The second version looks for the strings file named "MyStrings.strings" in the main bundle, and the last one let's you specify all the details. The macros use the first argument as both the key and the backstop translation.
CFBundle also has some ability to deal with Resource Manager-style resources. The CFBundle API
SInt32 CFBundleOpenBundleResourceFiles(CFBundleRef bundle, short *refNum, short *localizedRefNum);
will find and open (read-only) both a global and a localizable set of Resource Manager-style resources. The localizable resources should be in the data fork of a file called Localized.rsrc, located with the bundle's other localizable resources. The global resources should be in the data fork of a file called
The most support for executables that is provided by CFBundle is the ability to find a bundle's executable.
CFURLRef CFBundleCopyExecutableURL(CFBundleRef bundle);
CFURLRef CFBundleCopyAuxiliaryExecutableURL(CFBundleRef bundle, CFStringRef executableName);
The first of these functions locates and returns the URL for the main executable of the bundle. It returns the version of the executable appropriate for the current platform. The second function allows you to locate other helper executables that your bundle might have. Helper executables are stored in the same place as the main executable, and are also platform specific.
Often, CFBundles are used to package code that will be dynamically linked into an application. CFBundle provides API to actually dynamically load the code. The code loading API handles the details of what format the executable is. Is your bundle PEF/CFM? Is it Mach-o/dyld? Is it different things on different platforms? Do you care? Do you want to have to worry about it? Don't you have better things to do? Without CFBundle, not only do you have to know what format your executable is in, you also have to use a different set of APIs to deal with each flavor of binary.
CFBundle knows how to tell what type of binary it's dealing with and it knows how to use all the different APIs to deal with them. Note that CFBundle will always load a private copy of the executable where possible; this is currently possible in all cases except that of a Mach-o framework.
The main shared library or executable inside a bundle is identified by the CFBundleExecutable entry in the Info plist. Any bundle that has code inside it should have a key that identifies where to find the code. Since this is the case, loading and unloading code in a bundle is simple:
Boolean didLoad = CFBundleLoadExecutable(myBundle);
if (didLoad) {
// Do my thing...
CFBundleUnloadExecutable(myBundle);
}
Once the code is loaded, you might need to find some functions inside it:
MyFuncType funcPtr = (MyFuncType)CFBundleGetFunctionPointerForName(
myBundle, CFSTR("MyFunc"));
if (funcPtr) {
funcPtr(myArg1, myArg2);
}
If the bundle's code is not loaded when you try to look up a function, the code gets loaded first. Note that CFBundleGetFunctionPointerForName looks up symbols directly by name; it does not understand C++ name mangling.
In addition to these functions, there are also functions for asking whether a bundle's code is loaded already, and for looking up a whole list of functions at once.
The primitive code loading facility of CFBundle is great, but it's simplistic. If you have an existing plugin model, you probably can (and should) make use of this primitive API to abstract yourself from binary formats. If you are designing a new plug-in model for your application you should consider using CFPlugIn for anything but the simplest situations. There are also a few factors that might make it worth your while to convert to CFPlugIn from an existing custom plug-in model. First, your plug-in developers will need to Carboinize their plug-ins anyway (if they want them to work on Mac OS X), and if the conversion to CFPlugIn is a small enough task, then maybe it makes sense to have them convert to it at the same time. How hard the conversion is really depends on how close your existing model is to the new CFPlugIn model.
If you're desiging a new plug-in model for your application, or are willing to have your developers switch over, and would like some more help than the primitive code loading API of CFBundle then you should look at CFPlugIn. The original API that this plug-in model is based on was designed and implemented by the Finder team for their plug-in requirements. That original design has been adapted and made part of the CoreFoundation. In this section "PlugIn" with a capital "P" is used to refer to plug-ins that use the model provided by CFPlugIn.
The CFPlugIn model is compatible with the basics of Microsoft's COM architecture. What this means is that CFPlugIn Interfaces are laid out according to the COM guidelines and that all Interfaces must inherit from COM's IUnknown Interface. These are the only things that CFPlugIn shares with COM. Other COM concepts such as the IClassFactory Interface, aggregation, out-of-process servers, the Windows registry, etc... are not mapped.
CFPlugIns and CFBundles come in pairs. Every CFPlugIn has a CFBundle (but each CFBundle does not necessarily correspond to a CFPlugIn.) You can get the CFPlugIn from a CFBundle and vice-versa with the following functions:
CFPluginRef myPlugIn = CFBundleGetPlugIn(myBundle);
CFBundleRef myBundle = CFPlugInGetBundle(myPlugIn);
Since a CFPlugIn always has a CFBundle, but a CFBundle does not always have an associated CFPlugIn, CFBundleGetPlugIn() may return NULL, but CFPlugInGetBundle() should always return a valid CFBundle.
The PlugIn model in CFPlugIn relies on three key concepts:
Interfaces and Types are defined by the PlugIn host. A PlugIn host (typically an application) expects to find PlugIns that implement one or more specific Types defined by the host. PlugIn writers create Factories for each Type they want their PlugIn to support. A single PlugIn might support multiple Types. For instance, a Finder PlugIn writer might provide both a GetInfo Type Factory and a Preferences Type Factory in the same PlugIn (maybe their Preferences Type allows setting options that control the behavior of their GetInfo Type).
Interfaces, Types, and Factories are all identified by Universally Unique Identifiers (UUIDs).
A CFBundle that implements a PlugIn contains some extra information in its Info plist. To be a PlugIn it must be possible for a CFPlugIn to declare what Types it supports and what Factories it provides to create instances of those Types. This information can be declared statically in the Info plist of the PlugIn's bundle or it can be registered dynamically by code in the PlugIn. If the PlugIn wants to do dynamic registration, the code must be loaded immediately so the dynamic registration can take place, but if the PlugIn only wants to use static registration its code need not be loaded until the application actually wants to instantiate a Type. For this reason, static registration should be preferred when there's no overriding reason for using dynamic registration.
When a PlugIn host instantiates a Type, the result is a CFPlugInInstance. This is a CF type which represents one instance of a PlugIn Type. This means that what a PlugIn Factory creates is a CFPlugInInstance. When a Factory creates one of these things, it must provide it with a function pointer to a function that will be able to return the Interface function table for any Interfaces implemented by the Type.
It is probably easiest to understand the PlugIn model with an example. The first task for a PlugIn host developer is to define the Interfaces and Types that the host supports. To define an Interface you need a name for the Interface and a structure for the function table for that Interface. To define a Type all you need is a name. (The other interesting information about a Type is what Interfaces the Type is expected to implement. But this information is not needed at runtime and is not expressed as code in the header. It should be expressed as a comment though, just to be clear.) Here is a sample header which declares a Type and an Interface which the Type is expected to implement:
#include <CoreFoundation/CoreFoundation.h>
#define kTestTypeID (CFUUIDGetConstantUUIDWithBytes(NULL, 0xD7, 0x36, 0x95, 0x0A, 0x4D, 0x6E, 0x12, 0x26, 0x80, 0x3A, 0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67))
/* TestType objects must implement TestInterface */
#define kTestInterfaceID (CFUUIDGetConstantUUIDWithBytes(NULL, 0x67, 0x66, 0xE9, 0x4A, 0x4D, 0x6F, 0x12, 0x26, 0x9E, 0x9D, 0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67))
typedef struct TestInterfaceStruct {
IUNKNOWN_C_GUTS;
void (*fooMe)(void *this, Boolean flag);
} TestInterfaceStruct;
This header would typically be created by the PlugIn host application and made available to PlugIn writers. You'll notice that the Interface function "fooMe" that we have defined takes a "this" argument as its first parameter. This is not required, but it is a nice thing to do to help out PlugIn writers. By passing "this" to each Interface function you allow the PlugIn writer to implement in C++ and to have access to the object when the function executes in any language.
Notice that the Interface structure defined included IUNKNOWN_C_GUTS as its first element. This macro expands into the structure definition for the IUnknown Interface which all other Interfaces must include. In C++ this would be accomplished by deriving your Interface class from the IUnknown class.
Now that we have a Type and some Interfaces, let's look at how a PlugIn that supported this Type would be implemented. First, we'll look at the Info plist of the PlugIn (the plist is presented an ASCII format which is a bit easier to read than XML):
{
"CFBundleExecutable" = "MyTestPlugIn";
"CFPlugInDynamicRegistration" = "NO";
"CFPlugInFactories" = {
"68753A44-4D6F-1226-9C60-0050E4C00067" = "MyFactory";
};
"CFPlugInTypes" = {
"D736950A-4D6E-1226-803A-0050E4C00067" =
("68753A44-4D6F-1226-9C60-0050E4C00067");
};
}
This file requires some explanation. Basically the contents of this file is a dictionary of key/value pairs. The CFBundleExecutable key tells CFBundle what the name of the executable is and is used by the primitive code-loading API of CFBundle. The rest of the keys are specific to the PlugIn model.
The CFPlugInDynamicRegistration key indicates whether this PlugIn requires dynamic registration. For our example, we register everything statically, so we say NO for dynamic registration. If this key is set to YES then CFPlugIn will load the PlugIn's code and allow it to do its dynamic registration as soon as the associated CFBundle is created. For PlugIns that do dynamic registration, the optional CFPlugInDynamicRegisterFunction key can be set to the name of the function that should be called to do the dynamic registration. If this key is not provided for bundles which do dynamic registration, a function named "CFPlugInDynamicRegister" will be called. A PlugIn that does dynamic registration must either implement the default registration function or provide a different registration function with the CFPlugInDynamicRegisterFunction key.
The optional CFPlugInFactories key is used to statically register Factory functions. If present, the value of this key should be a dictionary whose keys are Factory UUIDs expressed in the standard string format for UUIDs and whose values are function names. Each key/value pair in this dictionary will cause a Factory to be registered when the associated CFBundle is created. In this example we have one Factory that has a UUID which the PlugIn developer generated and we declare that the Factory function in our PlugIn is named "MyFactory".
The optional CFPlugInTypes key is used to statically register the Factories that can create each supported Type. If present, the value of this key is a dictionary whose keys are Type UUIDs and whose values are arrays of Factory UUIDs. For each Type, there is a list of the Factories within the PlugIn which can create that Type. For our example, we declare that the Factory we registered with the CFPlugInFactories key can be used to create TestType objects (the TestType UUID was generated by the application developer).
Another optional key which is not used in this example is the CFPlugInUnloadFunction key. This key can be used to specify the name of a function in your PlugIn that should be invoked before the PlugIn's code is unloaded. If this key is not present, CFPlugIn will look for a function named "CFPlugInUnload" and invoke it if it exists.
To implement the PlugIn requires several things. We must provide the implementation for any Factory functions we registered. We also must provide implementations for all the functions of all the Interfaces that any of the Types supported by our PlugIn implement. We have to provide the interface function tables for the interface functions we implement. And finally, we have to provide a "getInterface" function so that we can create CFPlugInInstances that will be able to be queried for their Interface function tables. Here is the implementation for our example:
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFPriv.h>
#include "TestInterface.h"
#define kTestFactoryID (CFUUIDGetConstantUUIDWithBytes(NULL, 0x68, 0x75, 0x3A, 0x44, 0x4D, 0x6F, 0x12, 0x26, 0x9C, 0x60, 0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67))
typedef struct _MyType {
TestInterfaceStruct *_testInterface;
CFUUIDRef _factoryID;
UInt32 _refCount;
} MyType;
// Forward decls
static MyType *_allocMyType(CFUUIDRef factoryID);
static void _deallocMyType(MyType *this);
static HRESULT myQueryInterface(void *this, REFIID iid, LPVOID *ppv) {
CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
if (CFEqual(interfaceID, kTestInterfaceID)) {
((MyType *)this)->_testInterface->AddRef(this);
*ppv = this;
return S_OK;
} else if (CFEqual(interfaceID, IUnknownUUID)) {
((MyType *)this)->_testInterface->AddRef(this);
*ppv = this;
return S_OK;
} else {
*ppv = NULL;
return E_NOINTERFACE;
}
}
static ULONG myAddRef(void *this) {
((MyType *)this)->_refCount += 1;
return ((MyType *)this)->_refCount;
}
static ULONG myRelease(void *this) {
((MyType *)this)->_refCount -= 1;
if (((MyType *)this)->_refCount == 0) {
_deallocMyType((MyType *)this);
}
return ((MyType *)this)->_refCount;
}
static void myFooMe(void *this, Boolean flag) {
printf("myFooMe: instance 0x%x: I've been fooed. %s\n", (unsigned)this, (flag ? "YES" : "NOPE"));
}
static TestInterfaceStruct testInterfaceFtbl = { NULL, myQueryInterface, myAddRef, myRelease, myFooMe };
static MyType *_allocMyType(CFUUIDRef factoryID) {
MyType *newOne = (MyType *)malloc(sizeof(MyType));
newOne->_testInterface = &testInterfaceFtbl;
if (factoryID) {
newOne->_factoryID = CFRetain(factoryID);
CFPlugInAddInstanceForFactory(factoryID);
}
// One ref (on IUnknown)
newOne->_refCount = 1;
return newOne;
}
static void _deallocMyType(MyType *this) {
CFUUIDRef factoryID = this->_factoryID;
free(this);
if (factoryID) {
CFPlugInRemoveInstanceForFactory(factoryID);
CFRelease(factoryID);
}
}
void *MyFactory(CFAllocatorRef allocator, CFUUIDRef typeID) {
if (CFEqual(typeID, kTestTypeID)) {
MyType *result = _allocMyType(kTestFactoryID);
return result;
} else {
return NULL;
}
}
The code above includes a lot of glue code that would be unneccessary for C++ developers using a compiler with support for generating COM Interface layouts automatically.
There are some things worth mentioning about the above code.
The first thing we do is define the UUID for the factory we are going to supply. This is the same UUID that was used in the CFPlugInFactories key in the Info.plist.
Next the structure for "instances" of our TestType implementation is defined. This definition mimics the way a C++ object would be laid out, but this is not required.
After the structure is defined we implement the IUnknown interface functions which every PlugIn type must implement. These are relatively straight-forward. QueryInterface, in this example, is relying on the fact that the first pointer in our structure is an Interface, so returning a pointer to the MyType struct is the same as returning a pointer to out TestInterface. Types that implement more than one Interface would be more complicated (especially in C).
After the IUnknown stuff there is the implementation for the "fooMe" function from our TestInterface. In this example it just prints a message.
Next comes the static definition of the actual TestInterface function table. This table is filled in with the IUnknown and TestInterface functions we just implemented.
Following that are two utility functions that allow us to easily create and free MyType structures. The allocator will fill in the pointer to the Interface function table and will set the initial ref count to 1. It also takes care of registering the instance with the factory so that CFPlugIn will know not to unload the PlugIn's code while there are still instances. The free function frees the memory for MyType and unregisters the instance from the factory.
Finally, the actual Factory function just creates a new instance and returns a pointer to it (which also happens to be a pointer to the IUnknown Interface.) The MyFactory function must conform to the CFPlugInFactoryFunction prototype. Factory functions take a CFAllocator and a Type UUID.
The last piece of the example shows how a PlugIn host would load and use a PlugIn. This snippet of code assumes you already have a CFURL which identifies the location of the PlugIn.
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, CFSTR("/Network/Servers/custom/homes/ranch/mferris/Developer/PlugInTest/Plugin.plugin"), kCFURLPOSIXPathStyle, TRUE);
CFPlugInRef plugin = CFPlugInCreate(NULL, url);
if (!plugin) {
printf("Could not create CFPluginRef.\n");
} else {
CFArrayRef factories = CFPlugInFindFactoriesForPlugInType(kTestTypeID);
if ((factories != NULL) && (CFArrayGetCount(factories) > 0)) {
CFUUIDRef factoryID = CFArrayGetValueAtIndex(factories, 0);
IUnknownVtbl **iunknown = CFPlugInInstanceCreate(NULL, factoryID, kTestTypeID);
if (iunknown) {
TestInterfaceStruct **interface = NULL;
(*iunknown)->QueryInterface(iunknown, CFUUIDGetUUIDBytes(kTestInterfaceID), (LPVOID *)(&interface));
// Now we are done with IUnknown
(*iunknown)->Release(iunknown);
if (interface) {
(*interface)->fooMe(interface, TRUE);
(*interface)->fooMe(interface, FALSE);
// Now we are done with test interface
(*interface)->Release(interface);
} else {
printf("Failed to get interface.\n");
}
// MF:!! release the two interface refs...
} else {
printf("Failed to create instance.\n");
}
} else {
printf("Could not find any factories.\n");
}
CFRelease(plugin);
}
Loading and using PlugIns is pretty simple. CFPlugInFindFactoriesForPlugInType searches all the registered PlugIns and returns the list of all Factories that can create the requested Type. Once we find a Factory, we can ask it to create an instance of the TestType with CFPlugInInstanceCreate. CFPlugInInstanceCreate returns a pointer to the new insatnce's IUnknown Interface. Once we have a IUnknown, we can query it for the other Interfaces it supports. Finally, we can use the Interfaces to start invoking the functions.
One thing you might be wondering about is what would happen if there are multiple Factories to create a given Type. This example just blindly uses the first one. In general, it will be application dependent how to choose among Factories, but how much, if any, policy we should define and provide in this area is still under investigation.
The full API for CFBundle and CFPlugIn can be found in CFBundle.h and CFPlugIn.h in the CoreFoundation framework.
We are actively seeking comments from developers on this technology and it should be expected to evolve between now and the eventual release of Mac OS X. Feedback can be sent to cfplugin-feedback@group.apple.com.